/**
 * 
 */
package com.tcl.wip.client.repository;

import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.tcl.wip.client.WipApp;
import com.tcl.wip.client.sync.DbNode;
import com.tcl.wip.commons.constants.wip.MaintainStatus;
import com.tcl.wip.commons.hash.KetamaHashAlgorithm;

/**
 * @author zhaowen.zhuang
 * @Date Jan 19, 2015
 */
public class DatasourceManager {
    private final static Logger log = LoggerFactory.getLogger(DatasourceManager.class);


    private WipApp wipApp;

    private Map<Long, DbNode> dbNodeMap;

    private SortedMap<Long, WipDataSource> dataSourceMap;


    public DatasourceManager(WipApp wipApp) {
        this.wipApp = wipApp;
    }

    public void init(Map<Long, DbNode> dbNodes) {
        SortedMap<Long, WipDataSource> dataSourceMap = new TreeMap<Long, WipDataSource>();

        for (Entry<Long, DbNode> entry : dbNodes.entrySet()) {
            DbNode node = entry.getValue();
            WipDataSource dataSource = createDataSource(node);
            dataSourceMap.put(node.getHashCode(), dataSource);
        }

        this.dataSourceMap = Collections.unmodifiableSortedMap(dataSourceMap);
        this.dbNodeMap = Collections.unmodifiableMap(dbNodes);
    }

    /**
     * 更新本地数据源池
     * @param dbNodes 服务端的所有DB节点
     */
    public void update(Map<Long, DbNode> dbNodes) {
        SortedMap<Long, WipDataSource> dataSourceMap = new TreeMap<>(this.dataSourceMap);
        for (Entry<Long, DbNode> entry : dbNodes.entrySet()) {

            DbNode dbNode = entry.getValue();
            WipDataSource dataSource = dataSourceMap.get(dbNode.getHashCode());
            if (isMaintain(dbNode)) {
                dataSource.setMaintainStatus(dbNode.getMaintained());
                if (log.isInfoEnabled()) {
                    log.info("update DataSource maintain status, original maintain status="
                            + dataSource.getMaintainStatus());
                }
            }

            // 判断是否为新增节点，若是则创建数据源
            if (isNew(dbNode)) {
                dataSource = createDataSource(dbNode);
                if (log.isInfoEnabled()) {
                    log.info("new DataSource Object, original DataSource url="
                            + dataSource.getRawJdbcUrl());
                }

                dataSourceMap.put(dbNode.getHashCode(), dataSource);
                if (log.isInfoEnabled()) {
                    log.info("update DataSource, the new DataSource hash code="
                            + dbNode.getHashCode());
                }
            }
        }

        // 更新本地数据源池
        this.dataSourceMap = Collections.unmodifiableSortedMap(dataSourceMap);
        this.dbNodeMap = Collections.unmodifiableMap(dbNodes);
    }


    public DataSource getDatasource(String shardingKey) {
        if (log.isDebugEnabled()) {
            log.debug("sharding key:" + shardingKey);
        }

        KetamaHashAlgorithm ketama = KetamaHashAlgorithm.KETAMA_HASH;
        long hashcode = ketama.hash(ketama.md5(shardingKey), 0);

        WipDataSource wipDataSource = getDatasourceFromSortedMap(dataSourceMap, hashcode);

        if (wipDataSource.getMaintainStatus() != MaintainStatus.Normal) {
            // 如果当前结点正在扩容， 更新一次配置再返回
            Map<Long, DbNode> nodes = wipApp.syncDbNode();
            this.update(nodes);
        }

        return getDatasourceFromSortedMap(dataSourceMap, hashcode);
    }

    // 一致性 hash
    private WipDataSource getDatasourceFromSortedMap(SortedMap<Long, WipDataSource> map,
            long hashcode) {
        SortedMap<Long, WipDataSource> tailMap = map.tailMap(hashcode);
        if (tailMap.isEmpty()) {
            hashcode = dataSourceMap.firstKey();
        } else {
            hashcode = tailMap.firstKey();
        }

        return map.get(hashcode);
    }

    // 是不是新节点上的DB节点
    private boolean isNew(DbNode newDb) {
        DbNode oldDb = dbNodeMap.get(newDb.getId());
        if (null == oldDb) {
            return true;// 表示新节点上的DB
        }

        return false;
    }

    // 是不是正在维护的DB节点
    private boolean isMaintain(DbNode newDb) {
        DbNode oldDb = dbNodeMap.get(newDb.getId());
        if (null != oldDb) {
            if (oldDb.getMaintained() != newDb.getMaintained()) {
                return true;// 表示正在维护的DB节点
            }
        }

        return false;
    }


    private String getJdbcUrl(DbNode db) {
        String url =
                "jdbc:mysql://" + db.getIp() + ":" + db.getPort() + "/" + db.getName()
                        + "?useUnicode=true&characterEncoding=utf8";
        return url;
    }

    private WipDataSource createDataSource(DbNode db) {
        WipDataSource ds = new WipDataSource();
        ds.setUrl(getJdbcUrl(db));
        ds.setUsername(db.getUsername());
        ds.setPassword(db.getPassword());
        return ds;
    }


}
